//
//	MiscIfStack.m -- a simple data container for tracking if/then constructs
//		Written by Don Yacktman Copyright (c) 1995 by Don Yacktman.
//				Version 1.0.  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	


#import "_MiscIfPlaceHolder.h"
#import <misckit/MiscIfStack.h>

@implementation MiscIfStack
/*" A MiscIfStack is a specialized stack which may be used in a
dynamic interpreter to implement if/then/else/endif constructs.
The syntax is expected to have an "endif" match every single
"if" token.  The "else" token is optional.  The "if", "else", and
"endif" divide the interpreted code into blocks.  The MiscIfStack
can tell you whether or not the current block should be executed.
This includes correct handling of nested ifs.

To use a MiscIfStack, first send a -#{reset} message.  Then, whenever
an "if" token is encountered in the parsing, send a -#{startIf:}
message to the MiscIfStack.  If the conditional evaluated true, use YES
as the argument.  Us NO if it evaluated falsely.  When (and if) an
"else" token is encountered, send a -#{startElse} message.  Finally,
when the "endif" toekn is encountered, send a -#{endIf} message.

To determine if the current block should be executed, simply query
the MiscIfStack with a -#{currentConditionalIsActive} message.  If
YES is returned, then the code should be executed.
"*/

- reset
/*" Clears the MiscIfStack.  This should be called whenever a new
program is started.  Returns self.
"*/
{
	[self freeObjects];
	[self empty];
	return self;
}

- startIf:(BOOL)isActive
/*" Starts an "if" block.  If the "if" evaluates to true, then %{isActive}
should be YES, NO otherwise.  This will be used to determine the current
status of the MiscIfStack.  Returns self, or nil if an error occurs.
"*/
{
	id tempIf = [[_MiscIfPlaceHolder alloc] init];

	[tempIf setIfType:MISC_IF_START];
	if ([self currentConditionalIsActive]) {
		[tempIf setActivity:(isActive ? MISC_IF_ACTIVE : MISC_IF_INACTIVE)];
	} else {
		[tempIf setActivity:MISC_IF_DEAD];
	}
	if ([self pushObject:tempIf]) return self;
	return nil;
}

- startElse
/*" Begins an "else" block, changing the status to be the opposite
of the "if" block this is paired to.  Returns "self" if successful
and "nil" if an error occurs.  Errors include two "else" tokens in
a row or an "else" without an accompanying "if" token.
"*/
{
	id tempIf, lastIf = [self lastObject];
	Misc_IF_Activity newActivity;

	if ([lastIf ifType] == MISC_IF_ELSE) {
		[self doubleElseIfError];
		return nil;
	}
	if (!lastIf) {
		[self elseWithoutIfError];
		return nil;
	}
	tempIf = [[_MiscIfPlaceHolder alloc] init];
	[tempIf setIfType:MISC_IF_ELSE];
	switch ([lastIf activity]) {
		case MISC_IF_ACTIVE : newActivity = MISC_IF_INACTIVE; break;
		case MISC_IF_INACTIVE : newActivity = MISC_IF_ACTIVE; break;
		// dead ifs stay dead in the else.  It means that this if/else
		// pair is inside of a conditional that evaluated false.
		default : newActivity = MISC_IF_DEAD; break;
	}
	[tempIf setActivity:newActivity];
	if ([self pushObject:tempIf]) return self;
	return nil;
}

- endIf
/*" Ends an "if-else" block, returning the MiscIfStack to the status
of the block before the "if" to be cleared was encountered.  Returns
self if successful and nil if there was an error, such as an "endif"
without a matching "if".
"*/
{
	if (![self lastObject]) { // error-- "endif" without "if"
		[self endWithoutIfError];
		return nil;
	}
	// Discard an "else" if there is one.
	if ([[self lastObject] ifType] == MISC_IF_ELSE) {
		[[self popObject] free];
	}
	// And discard the "if" that matches us.  We don't need to check
	// since the checking when we push guarantees things for us.
	[[self popObject] free];
	return self;
}

- (BOOL)currentConditionalIsActive
/*" Returns YES if the "if" or "else" block on top of the stack
evaluates true and should be executed.  Returns NO otherwise.
"*/
{ // Inactive if "dead" or explicitly inactive.  Active if explicitly active.
	if ([self count] < 1) return YES;
	return ([[self lastObject] activity] == MISC_IF_ACTIVE);
}

- (void)endWithoutIfError
/*" Prints a diagnostic error message to the console if
an "endif" token is found that does not have a matching "if" token.
"*/
{
	fprintf(stderr, "MiscIfStack:  \"endif\" without \"if\" encountered.\n");
}

- (void)elseWithoutIfError
/*" Prints a diagnostic error message to the console if
an "else" token is found that does not have a matching "if" token.
"*/
{
	fprintf(stderr, "MiscIfStack:  \"else\" without \"if\" encountered.\n");
}

- (void)doubleElseIfError
/*" Prints a diagnostic error message to the console if
two "else" tokens are found in a row.  This is syntactically the
same as having an "else" without a matching "if".
"*/
{
	fprintf(stderr, "MiscIfStack:  \"else\" use twice in a row.\n");
}

@end
